/*
 * Copyright (C) Francisco Morero Peyrona. All rights reserved.
 * 
 * This software is published under the terms of Open Source License version 1.1, a copy of which has been included with this
 * distribution in the License.txt file.
 */

package jdbcmanager.dbtools.agentwise;

import java.io.*;
import java.sql.*;
import java.util.*;

import jdbcmanager.app.*;

/**
 * @author Francisco Morero Peyrona
 */

public class AgentDBMS implements Comparable
{
	private AgentMetaData	  meta	 = null;
	private AgentSQL		  sql	 = null;
	private AgentDefinitionIO io	 = null;
	private Driver			  driver = null;

	// private because the instances of this class are created by the static method "getAgents()"
	private AgentDBMS( File f )
	{
		if( f != null )
		{
			try
			{
				this.io = new AgentDefinitionIO( f );
			}
			catch( IOException exc )
			{
				//this.bOperable = false;
				App.showDebugException( exc );
			}
		}
	}

	public String getDriverFamiliarName()
	{
		return this.io.getDriverName();
	}

	public String getDriverId()
	{
		return this.io.getDriverID();
	}

	public String getAgentMetaDataClassName()
	{
		return this.io.getAgentMetaDataClassName();
	}

	public String getAgentSQLClassName()
	{
		return this.io.getAgentSQLClassName();
	}

	public AgentMetaData getAgentMetaData()
	{
		return this.meta;
	}

	public AgentSQL getAgentSQL()
	{
		return this.sql;
	}

	public String getAgentsJAR()
	{
		return this.io.getAgentsJAR();
	}

	public String getConnectionSyntax()
	{
		return this.io.getConnectionSyntax();
	}

	public String getDriverClassName()
	{
		return this.io.getDriverClassName();
	}

	public String[] getJARs()
	{
		return this.io.getDriverJARs();
	}

	/**
	 * Provides a hint about future operability.
	 * <p>
	 * If this method returns <code>false</code>, it is sure that this agent will not be opreable. <br>
	 * If this method returns <code>true</code>, this agent could or could not be operable.
	 * <p>
	 * It is only after <code>initDriver(...)</code> is called that can be known if the driver is operable.
	 * 
	 * @return <code>false</code>, if the agent will not be opreable, <code>true</code> if it could be operable
	 */
	public boolean couldOperate()
	{
		boolean bCouldOperate = true;

		// Check that all needed JARs are available
		String[] asJAR = this.io.getDriverJARs();

		if( asJAR.length > 0 )
		{
			for( int n = 0; n < asJAR.length; n++ )
			{
				File fJAR = new File( asJAR[n] );

				if( !fJAR.exists() || !fJAR.isFile() )
				{
					bCouldOperate = false;
					break;
				}
			}
		}
		else
		{
			// Only Sun JDBC-ODBC driver does not need to specify JARs
			bCouldOperate = this.io.getDriverClassName().equals( "sun.jdbc.odbc.JdbcOdbcDriver" );
		}

		// Check that AgentMetaData and AgentSQL exist
		if( this.io.getAgentsJAR().length() > 0 )
		{
			// If a JAR is defined, it is assumed that the JAR will cotain at least both .class files
			File fJAR = new File( App.getAgentsDir(), this.io.getAgentsJAR() );

			if( !fJAR.exists() || !fJAR.isFile() )
				bCouldOperate = false;
		}
		else
		{
			// Check that AgentSQL class file exist
			File fSQL = new File( App.getAgentsDir(), this.io.getAgentSQLClassName() );

			if( !fSQL.exists() || !fSQL.isFile() )
				bCouldOperate = false;

			// Check that AgentMetaData class file exist
			File fMETA = new File( App.getAgentsDir(), this.io.getAgentMetaDataClassName() );

			if( !fMETA.exists() || !fMETA.isFile() )
				bCouldOperate = false;
		}

		return bCouldOperate;
	}

	/**
	 * Initialises JDBC driver.
	 * 
	 * @param sURL
	 * @param sUser
	 * @param sPassword
	 * @return A connecton
	 */
	public Connection initDriver( String sURL, String sUser, String sPassword ) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException
	{
		Connection conn = null;

		this.driver = DriverLoader.load( this.io.getDriverClassName(), this.io.getDriverJARs() ); // load(...) can return null

		if( this.driver != null )
		{
			conn = getConnection( sURL, null, sUser, sPassword );

			if( conn != null )
			{
				// Loads at run-time both: AgentMetaData and AgentSQL
				this.meta = (new AgentLoader()).loadMeta( this.io.getAgentMetaDataClassName() );
				this.sql = (new AgentLoader()).loadSQL( this.io.getAgentSQLClassName() );

				this.meta.setMetaData( conn.getMetaData() );
			}
		}

		return conn;
	}
	
	/**
	 * Stablishes a connection based on passed parameters (it is assumed that driver is loaded).
	 * <p>
	 * This method is called once per each catalog in the DBMS that is accessed.
	 * 
	 * @param sURL
	 * @param sCatalog
	 * @param sUser
	 * @param sPassword
	 * @return A connection
	 * @throws SQLException
	 */
	public Connection getConnection( String sURL, String sCatalog, String sUser, String sPassword ) throws SQLException
	{
		Properties prop = new Properties();

		if( sUser != null )
			prop.put( "user", sUser );

		if( sPassword != null )
			prop.put( "password", sPassword );

		Connection conn = this.driver.connect( sURL, prop );

		if( sCatalog != null )
			conn.setCatalog( sCatalog );
		
		return conn;
	}

	/**
	 *  
	 */
	public int compareTo( Object obj ) throws ClassCastException
	{
		if( !(obj instanceof AgentDBMS) )
			throw new ClassCastException( "Can't compare an instance of '" + getClass().getName() + "' with an instance of '" + obj.getClass().getName() + '\'' );

		return this.io.getDriverName().compareTo( ((AgentDBMS) obj).getDriverFamiliarName() );
	}

	//-------------------------------------------------------------------------//
	// SPECIAL STATIC METHODS:
	//    getAgents() --> Return available agents
	//    createAgent(...) --> Creates a file with an Agent DBMS definition
	//-------------------------------------------------------------------------//

	/**
	 * An array of available instances of this class that can be created.
	 * 
	 * @return the array
	 */
	public static AgentDBMS[] getAgents()
	{
		Vector vAgents = new Vector();
		File[] afAgent = AgentDefinitionIO.getAgentDefinitions();

		for( int n = 0; n < afAgent.length; n++ )
		{
			AgentDBMS agent = new AgentDBMS( afAgent[n] );

			if( agent.io.isWellFormed() )
				vAgents.add( agent );
			else
				App.showMessage( "Integrity Problem", 
						         "File '" + afAgent[n].getAbsolutePath() + "'\n" + 
						         "Appears to be corrupted or is not well formed.\n"+ 
								 "Correspondant DBMS Agent can not be created, neither used." );
		}

		// From Vector to Array
		AgentDBMS[] aoAgent = new AgentDBMS[vAgents.size()];

		vAgents.copyInto( aoAgent );
		Arrays.sort( aoAgent );

		return aoAgent;
	}

	/**
	 * Finds the agent based on its DriverId (this is an unique identifier for an agent)
	 * 
	 * @param sDriverId
	 *            Identifier to look for
	 * @return If found the agent instance, otherwise <code>null</code>
	 */
	public static AgentDBMS getAgent4DriverId( String sDriverId )
	{
		AgentDBMS[] aAgent = getAgents();
		AgentDBMS agent = null;

		sDriverId = sDriverId.trim();

		for( int n = 0; n < aAgent.length; n++ )
		{
			if( aAgent[n].getDriverId().equals( sDriverId ) )
			{
				agent = aAgent[n];
				break;
			}
		}

		return agent;
	}

	/**
	 * Creates a file with the information that defines an AgentDBMS.
	 * <p>
	 * Used for both: create a new one and to edit an existing one (in this case, the file is overwritten).
	 * 
	 * @param sProductName
	 * @param sDriverID
	 * @param sConnectionSyntax
	 * @param sDriverClassName
	 * @param sJARs
	 * @param sAgentMetaDataClassName
	 * @param sAgentSQLClassName
	 */
	public static void createAgent( String sProductName, String sDriverID, String sConnectionSyntax, String sDriverClassName, 
			                        String[] sJARs, String sAgentMetaDataClassName,	String sAgentSQLClassName, String sAgentsJAR )
	{
		if( sAgentMetaDataClassName == null )
			sAgentMetaDataClassName = ""; // The default one is used

		if( sAgentSQLClassName == null )
			sAgentSQLClassName = ""; // The default one is used

		AgentDefinitionIO newIO = new AgentDefinitionIO();
		newIO.setDriverName( sProductName );
		newIO.setDriverID( sDriverID );
		newIO.setAgentMetaDataClassName( sAgentMetaDataClassName );
		newIO.setAgentSQLClassName( sAgentSQLClassName );
		newIO.setAgentsJAR( sAgentsJAR );
		newIO.setConnectionSyntax( sConnectionSyntax );
		newIO.setDriverClassName( sDriverClassName );
		newIO.setDriverJARs( sJARs );
		newIO.save();
	}
}